﻿using System.Security.AccessControl;
using Microsoft.Web.Administration;

namespace Pap.Web
{
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;
    using System.Web.Configuration;
    using Pap.Core.Sync.Configuration;

    [RunInstaller(true)]
    public partial class ConfigInstaller : System.Configuration.Install.Installer
    {
        #region [Parameters]

        private const string LocalDatabaseServer = "Server";
        private const string LocalDatabaseName = "DBName";
        private const string LocalDatabaseUsername = "username";
        private const string LocalDatabasePassword = "password";

        private const string ServerDatabaseServer = "SyncServerName";
        private const string ServerDatabaseName = "SyncDBName";
        private const string ServerDatabaseUsername = "SyncUserName";
        private const string ServerDatabasePassword = "SyncPassword";

        private const string FolderPath = "Folder";
        private const string IsMainCommittee = "IsMain";
        private const string ApplicationName = "AppName";
        //private const string VacationFilePath = "VacationFilePath";
        private const string DatabaseBackupPath = "DBBackup";
        private const string CommitteeName = "CommName";
        private const string ApplicationPoolName = "AppPool";

        #endregion

        #region [ Fields ]

        private Configuration _configFile;
        private string _logFilePath = null;

        private SqlConnectionStringBuilder _localConnectionString;
        private SqlConnectionStringBuilder _serverConnectionString;

        private Guid _CommitteeID;

        #endregion

        #region [ Constructors ]

        public ConfigInstaller()
        {
            InitializeComponent();
        }

        #endregion Constructors

        #region [ Overrids ]

        public override void Install(IDictionary stateSaver)
        {
#if DEBUG
            Debugger.Break();
#endif
            LogMessage("Configuration Begins");
            base.Install(stateSaver);

            UpdateFolderSecurity();

            Prepare();

            //Create Database [Done]
            CreateDatabase();

            //Configure Database 
            ConfigureDatabase();

            //Update Web.Config
            UpdateConfig();

            _configFile.Save();
            LogMessage("Configuration Ends");
        }

        public override void Uninstall(IDictionary savedState)
        {
            Debugger.Break();
            base.Uninstall(savedState);
        }

        #endregion

        #region [ Helps ]

        private void Prepare()
        {
            _localConnectionString = new SqlConnectionStringBuilder();
            _localConnectionString.DataSource = Context.Parameters[LocalDatabaseServer];
            _localConnectionString.InitialCatalog = Context.Parameters[LocalDatabaseName];

            if (!string.IsNullOrEmpty(Context.Parameters[LocalDatabaseUsername]))
                _localConnectionString.UserID = Context.Parameters[LocalDatabaseUsername];
            if (!string.IsNullOrEmpty(Context.Parameters[LocalDatabasePassword]))
                _localConnectionString.Password = Context.Parameters[LocalDatabasePassword];

            if (string.IsNullOrEmpty(Context.Parameters[LocalDatabaseName]) ||
                string.IsNullOrEmpty(Context.Parameters[LocalDatabasePassword]))
                _localConnectionString.IntegratedSecurity = true;
            LogMessage("Local connction string " + _localConnectionString.ToString());
            //-----------------------------------------------------------------------------------------------------------
            _serverConnectionString = new SqlConnectionStringBuilder();
            _serverConnectionString.DataSource = Context.Parameters[ServerDatabaseServer];
            _serverConnectionString.InitialCatalog = Context.Parameters[ServerDatabaseName];

            if (!string.IsNullOrEmpty(Context.Parameters[ServerDatabaseUsername]))
                _serverConnectionString.UserID = Context.Parameters[ServerDatabaseUsername];
            if (!string.IsNullOrEmpty(Context.Parameters[ServerDatabasePassword]))
                _serverConnectionString.Password = Context.Parameters[ServerDatabasePassword];

            if (string.IsNullOrEmpty(Context.Parameters[ServerDatabaseName]) ||
                string.IsNullOrEmpty(Context.Parameters[ServerDatabasePassword]))
                _serverConnectionString.IntegratedSecurity = true;

            LogMessage("Server connction string " + _serverConnectionString.ToString());
        }

        private void CreateDatabase()
        {
            LogMessage("Creating Database");

            var dir = Context.Parameters[FolderPath];
            Process p = new Process();
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.CreateNoWindow = true;
            p.EnableRaisingEvents = true;
            p.OutputDataReceived += (o, e) => LogMessage(e.Data);
            p.ErrorDataReceived += (o, e) => LogMessage(e.Data);
            p.StartInfo.FileName = dir + @"\cmd\vsdbcmd.exe ";
            string strParams =
                String.Format(
                    "/a:Deploy /ManifestFile:\"{0}\\cmd\\release\\Pap.Database.deploymanifest\" /cs:\"server={1};trusted_connection=true;\" /p:TargetDatabase={2} /dd:+ /p:AlwaysCreateNewDatabase=True /p:AbortOnFirstError=False",
                    dir, Context.Parameters[LocalDatabaseServer], Context.Parameters[LocalDatabaseName]);
            p.StartInfo.Arguments = strParams;

            p.Start();
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
            p.WaitForExit();

            if (p.ExitCode == 1)
            {
                LogMessage("Database did not installed Correctly");
                throw new InvalidOperationException("Database is not installed Correctly");
            }
            else
                LogMessage("Database Created");
        }

        private void ConfigureDatabase()
        {
            LogMessage("Configuring Database");
            SqlConnection conn = new SqlConnection(_localConnectionString.ToString());

            conn.Open();
            try
            {
                LogMessage("Creating NT AUTHORITY\\SYSTEM");
                // Create User
                SqlCommand createUserCmd =
                    new SqlCommand(
                        @"
                IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'NT AUTHORITY\SYSTEM')
                CREATE LOGIN [NT AUTHORITY\\SYSTEM] FROM WINDOWS",
                        conn);
                createUserCmd.ExecuteNonQuery();
            }
            catch (Exception userEx)
            {
                LogMessage("Error createing NT AUTHORITY\\SYSTEM " + userEx.ToString());
            }
            //----------------------------------------------------------------------------------------------
            try
            {
                LogMessage("Grant Access to NT AUTHORITY\\SYSTEM");
                //Grant Access to user
                SqlCommand grandAccessCmd = new SqlCommand(
                    "EXEC sp_addrolemember N'db_owner', N'NT AUTHORITY\\SYSTEM'", conn);
                grandAccessCmd.ExecuteNonQuery();
            }
            catch (Exception accessEx)
            {
                LogMessage("Error Granting access to user NT AUTHORITY\\SYSTEM " + accessEx.ToString());
            }
            //----------------------------------------------------------------------------------------------
            try
            {
                LogMessage("Alter database Isolation Level");
                // Set islation level
                SqlCommand setSnapshotIsolationCmd =
                    new SqlCommand(
                        " ALTER DATABASE " + Context.Parameters[LocalDatabaseName] + " SET ALLOW_SNAPSHOT_ISOLATION ON",
                        conn);
                setSnapshotIsolationCmd.ExecuteNonQuery();
            }
            catch (Exception snapshotEx)
            {
                LogMessage("Error Altering Isolation Level " + snapshotEx.ToString());
            }
            //----------------------------------------------------------------------------------------------
            try
            {
                LogMessage("Calling sp_ConfigureCommittee");
                //Create Committee
                SqlCommand createCommittee = new SqlCommand("sp_ConfigureCommittee", conn);
                createCommittee.CommandType = System.Data.CommandType.StoredProcedure;
                createCommittee.Parameters.AddWithValue("@CommitteeName", Context.Parameters[CommitteeName]);
                createCommittee.Parameters.AddWithValue("@IsMainCommittee",
                                                        Context.Parameters[IsMainCommittee].CompareTo("2"));
                SqlParameter outputIdParam = new SqlParameter("@CommID", SqlDbType.UniqueIdentifier)
                                                 {
                                                     Direction = ParameterDirection.Output,
                                                     Value = DBNull.Value
                                                 };
                createCommittee.Parameters.Add(outputIdParam);
                createCommittee.ExecuteNonQuery();

                _CommitteeID = new Guid(outputIdParam.Value.ToString());
            }
            catch (Exception committeeEx)
            {
                LogMessage("Error in sp_ConfigureCommittee " + committeeEx.ToString());
                throw new InvalidOperationException("Error Configuring Committee or global settings");
            }
            conn.Close();
        }

        private void UpdateConfig()
        {
            try
            {
                _configFile = WebConfigurationManager.OpenWebConfiguration("/" + Context.Parameters[ApplicationName]);
                AppDomain.CurrentDomain.AssemblyResolve +=
                    (sender, args) =>
                    Assembly.LoadFile(Path.GetDirectoryName(_configFile.FilePath) + "\\bin\\Pap.Core.dll");
                LogMessage("Update Web Config");
                //-----------------------------------------------------------------------------------------------------------
                _configFile.AppSettings.Settings["VacationFilePath"].Value = "~/App_Data/VacationsOfMonths.xml";
                _configFile.AppSettings.Settings["DBBackupRstoreLocation"].Value =
                    Context.Parameters[DatabaseBackupPath];
                _configFile.AppSettings.Settings["CommitteeID"].Value = _CommitteeID.ToString();
                //-----------------------------------------------------------------------------------------------------------
                LogMessage("Update ConnectionString Config");
                _configFile.ConnectionStrings.ConnectionStrings["ConnectionString1"].ConnectionString =
                    _localConnectionString.ToString();
                _configFile.ConnectionStrings.ConnectionStrings["PapDBConnectionStringReports"].ConnectionString =
                    _localConnectionString.ToString();
                _configFile.ConnectionStrings.ConnectionStrings["Server_ConnectionString"].ConnectionString =
                    _serverConnectionString.ToString();
                //-----------------------------------------------------------------------------------------------------------
                LogMessage("Update Syncronization Config");
                SyncronizationSection sec = SyncronizationSection.FromConfig(_configFile);
                sec.ServerPeer.Params["RemotePeer"].Value = _CommitteeID.ToString();
            }
            catch (Exception ex)
            {
                LogMessage("Error Writing in config files");
                throw new InvalidOperationException("Error Writing in configuration");
            }
        }

        private void UpdateFolderSecurity()
        {
            //ServerManager serverManager = new ServerManager();
            //string username =
            //    serverManager.ApplicationPools[Context.Parameters[ApplicationPoolName]].ProcessModel.UserName;

            var foldePath = Context.Parameters[FolderPath];
            //- remove Read only from xml file.
            DirectoryInfo directoryInfo = new DirectoryInfo(Path.Combine(foldePath, "App_Data"));
            directoryInfo.CreateSubdirectory("logs");
            var accessControl = directoryInfo.GetAccessControl(AccessControlSections.All);
            accessControl.AddAccessRule(new FileSystemAccessRule("Everyone", FileSystemRights.FullControl,
                                                                 InheritanceFlags.ContainerInherit |
                                                                 InheritanceFlags.ObjectInherit,
                                                                 PropagationFlags.InheritOnly,
                                                                 AccessControlType.Allow));
            directoryInfo.SetAccessControl(accessControl);
        }

        private void LogMessage(string message)
        {
#if DEBUG
            if (_logFilePath == null)
                _logFilePath = Path.Combine(this.Context.Parameters[ConfigInstaller.FolderPath],
                                            string.Format("Install-log-{0}.log",
                                                          DateTime.Now.ToString("MM-dd-yy-HH-mm-ss")));

            File.AppendAllText(_logFilePath,
                               string.Format("LOG:{0}\t{1}{2}", DateTime.Now.ToShortTimeString(), message,
                                             Environment.NewLine));
#else
            EventLog.WriteEntry("PAP Installation", message);
#endif

        }

        #endregion
    }
}